module XvgParser

using DataFrames
using Utils
import CSV

export read_xvg, xvg2csv, XVG

mutable struct XVG
    xvgdata::DataFrame
    _title::String
    _xlabel::String
    _ylabel::String
    _legends::Vector{String}
    istall::Bool
end

function latexify_label(label)
    label = replace(label, "\\S" => "^{")
    label = replace(label, "\\N" => "}")
    label = replace(label, " " => "\\ ")
    return '$' * label * '$'
end

function read_xvg(input_xvg::AbstractString)
    println("Reading $input_xvg...")
    _lines = filter(x -> !startswith(x, '#') && length(strip(x)) != 0, readlines(input_xvg))
    # 处理参数
    _paras = filter(x -> startswith(x, '@'), _lines)
    _title = match(r".*\"(.*)\".*", _paras[findfirst(x -> split(x)[2] == "title", _paras)])[1]
    _xlabel = match(r".*\"(.*)\".*", _paras[findfirst(x -> split(x)[2] == "xaxis", _paras)])[1]
    _ylabel = match(r".*\"(.*)\".*", _paras[findfirst(x -> split(x)[2] == "yaxis", _paras)])[1]
    _legends = [match(r".*\"(.*)\".*", l)[1] for l in filter(x -> startswith(x, r"@ s\d+"), _paras)]
    if length(_legends) == 0
        _legends = [""]
    end
    
    _data_lines_index = findall(x -> !startswith(x, '@'), _lines)
    _data_items = reshape([parse.(Float64, l) for l in split(_lines[_data_lines_index[1]])], 1, :)
    for ln in _data_lines_index[2:end]
        _data_items = vcat(_data_items, reshape([parse.(Float64, l) for l in split(_lines[ln])], 1, :))
    end
    xvgdata = DataFrame(_data_items, [Symbol(_xlabel), Symbol.(_legends)...])
    finish_hint("Finished reading $input_xvg.\n")
    return XVG(xvgdata, _title, _xlabel, _ylabel, _legends, false)
end

function xvg2csv(xvg::XVG, path::AbstractString)
    CSV.write(path, xvg.xvgdata)
end

function append_xvg!(xvg::XVG, path::AbstractString)
    new_xvg = read_xvg(path)
    if xvg._ylabel != new_xvg._ylabel
        error_hint("Error: ylabel of the two xvg files different: $(xvg._ylabel), $(new_xvg._ylabel).\n")
    end
    append!(xvg._legends, new_xvg._legends)
    if xvg.xvgdata[:, 1] != new_xvg.xvgdata[:, 1]
        # 处理一下交集问题
        warning_hint("Two x axis not consistent. Take which way to append?\n")
        println(" 0 intersection    (only rows with keys on both sides are returned)")
        println(" 1 remain current  (rows with keys only on the current xvg are also returned)")
        println(" 2 remain new      (rows with keys only on the new xvg are also returned)")
        println(" 3 union           (rows with elements missing in any of the two xvgs are also returned)")
        para = sel_fun(Int32)
        if para == 0
            xvg.xvgdata = innerjoin(xvg.xvgdata, new_xvg.xvgdata, on=xvg._xlabel, makeunique=true)
        elseif para == 1
            xvg.xvgdata = leftjoin(xvg.xvgdata, new_xvg.xvgdata, on=xvg._xlabel, makeunique=true)
        elseif para == 2
            xvg.xvgdata = rightjoin(xvg.xvgdata, new_xvg.xvgdata, on=xvg._xlabel, makeunique=true)
        elseif para == 3
            xvg.xvgdata = outerjoin(xvg.xvgdata, new_xvg.xvgdata, on=xvg._xlabel, makeunique=true)
        end
    else
        xvg.xvgdata = innerjoin(xvg.xvgdata, new_xvg.xvgdata, on=xvg._xlabel, makeunique=true)
    end
    xvg._legends = names(xvg.xvgdata)
end

function toggle_tall_wide!(xvg::XVG)
    if xvg.istall
        xvg.xvgdata = unstack(xvg.xvgdata, Symbol(xvg._xlabel), Symbol(xvg._title), Symbol(xvg._ylabel), fill=0.0)
    else
        xvg.xvgdata = stack(xvg.xvgdata, Symbol.(xvg._legends), Symbol(xvg._xlabel), variable_name=Symbol(xvg._title), value_name=Symbol(xvg._ylabel))
        sort!(xvg.xvgdata, Symbol(xvg._xlabel))
    end
    xvg.istall = !xvg.istall
end

end
